/**
 ** @file       DBFoundation.m
 ** @brief      An Objective-C 2.0 wrapper for SQLite 3.4.0 or later.
 ** @version    1.0
 **
 ** @section    db_license      MIT License
 **
 ** Copyright (c) 2008-2009 Ben Rimmington <http://ben-rimmington.googlecode.com/>
 **
 ** Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
 ** to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
 ** and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 **
 ** The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 **
 ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 ** DEALINGS IN THE SOFTWARE.
 **/

#import <objc/runtime.h>
#import "DBFoundation.h"

@interface DBFunction ()
+ (NSArray *)functionsWithConnection:(DBConnection *)connection;
+ (id)invokeFunction:(DBFunction *)function userInfo:(NSMutableDictionary *)userInfo valueList:(sqlite3_value **)valueList valueCount:(NSUInteger)valueCount;
+ (void)setUserInfo:(NSMutableDictionary *)userInfo forScalarWithContext:(sqlite3_context *)context valueCount:(NSUInteger)valueCount;
+ (NSMutableDictionary *)userInfoForScalarWithContext:(sqlite3_context *)context valueCount:(NSUInteger)valueCount;
+ (NSMutableDictionary *)userInfoForAggregateWithContext:(sqlite3_context *)context;
+ (id)valueFromSQLite:(sqlite3_value *)value;
@property (nonatomic, readwrite, assign) __weak DBConnection *connection;
@property (nonatomic, readwrite, assign) SEL selector;
@end

@interface DBConnection ()
- (NSException *)exceptionWithFormat:(NSString *)format, ...;
@property (nonatomic, readwrite, copy) NSString *path;
@property (nonatomic, readwrite, assign) sqlite3 *handle;
@property (nonatomic, readwrite, retain) NSArray *functions;
@end

@interface DBStatement ()
- (id)valueAtColumnIndex:(NSUInteger)columnIndex;
@property (nonatomic, readwrite, assign) __weak DBConnection *connection;
@property (nonatomic, readwrite, copy) NSString *sql;
@property (nonatomic, readwrite, assign) sqlite3_stmt *handle;
@property (nonatomic, readwrite, retain) NSMutableDictionary *row;
@property (nonatomic, readwrite, assign) unsigned long mutationCount;
@end

/// @cond
NSString *const DBException           = @"DBException";
NSString *const SQLiteErrorCodeKey    = @"SQLiteErrorCodeKey";
NSString *const SQLiteErrorMessageKey = @"SQLiteErrorMessageKey";
NSString *const SQLiteResultKey       = @"SQLiteResultKey";
NSString *const SQLiteVersionKey      = @"SQLiteVersionKey";
/// @endcond

#define DBTypeIsObject(objCType) (0 == strcmp((objCType), @encode(id)))
#define DBTypeIsVoid(objCType)   (0 == strcmp((objCType), @encode(void)))

static void DBFunctionInvokeScalar(sqlite3_context *context, int valueCount, sqlite3_value **valueList) {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    DBFunction *function = sqlite3_user_data(context);
    @try {
        NSMutableDictionary *userInfo = [DBFunction userInfoForScalarWithContext:context valueCount:valueCount];
        id result = [DBFunction invokeFunction:function userInfo:userInfo valueList:valueList valueCount:valueCount];
        [result setResultOfFunctionWithContext:context];
        [DBFunction setUserInfo:userInfo forScalarWithContext:context valueCount:valueCount];
    } @catch (NSException *exception) {
        sqlite3_result_error(context, [[NSString stringWithFormat:@"%@ %@", function, [exception reason]] UTF8String], -1);
    }
    [pool drain];
}

static void DBFunctionStepAggregate(sqlite3_context *context, int valueCount, sqlite3_value **valueList) {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    DBFunction *function = sqlite3_user_data(context);
    NSMutableDictionary *userInfo = [DBFunction userInfoForAggregateWithContext:context];
    @try {
        if (![userInfo valueForKey:SQLiteErrorMessageKey]) {
            [DBFunction invokeFunction:function userInfo:userInfo valueList:valueList valueCount:valueCount];
        }
    } @catch (NSException *exception) {
        [userInfo setValue:[exception reason] forKey:SQLiteErrorMessageKey];
    }
    [pool drain];
}

static void DBFunctionFinalizeAggregate(sqlite3_context *context) {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    DBFunction *function = sqlite3_user_data(context);
    NSMutableDictionary *userInfo = [DBFunction userInfoForAggregateWithContext:context];
    @try {
        if ([userInfo valueForKey:SQLiteErrorMessageKey]) {
            @throw [NSException exceptionWithName:DBException reason:[userInfo valueForKey:SQLiteErrorMessageKey] userInfo:nil];
        }
        [userInfo setValue:[NSNull null] forKey:SQLiteResultKey];
        id result = [DBFunction invokeFunction:function userInfo:userInfo valueList:NULL valueCount:0];
        [result setResultOfFunctionWithContext:context];
    } @catch (NSException *exception) {
        sqlite3_result_error(context, [[NSString stringWithFormat:@"%@ %@", function, [exception reason]] UTF8String], -1);
    } @finally {
        CFRelease(userInfo);
    }
    [pool drain];
}

static int DBConnectionShouldInterrupt(void *connection) {
    return [((DBConnection *)connection).delegate connectionShouldInterrupt:connection];
}

static int DBConnectionShouldRetryLock(void *connection, int retryCount) {
    return [((DBConnection *)connection).delegate connection:connection shouldRetryLockWithCount:retryCount];
}

static int DBCollationCompare(void *selector, int length1, const void *bytes1, int length2, const void *bytes2) {
    NSComparisonResult result = NSOrderedSame;
    NSString *string1 = [[NSString alloc] initWithBytes:bytes1 length:length1 encoding:NSUTF8StringEncoding];
    NSString *string2 = [[NSString alloc] initWithBytes:bytes2 length:length2 encoding:NSUTF8StringEncoding];
    if (string1 && string2) {
        typedef NSComparisonResult (*DBCompareIMP)(id, SEL, id);
        DBCompareIMP compare = (DBCompareIMP)[NSString instanceMethodForSelector:selector];
        result = compare(string1, selector, string2);
    }
    [string1 release];
    [string2 release];
    return result;
}

static void DBCollationNeeded(void *unused, sqlite3 *handle, int encoding, const char *name) {
    NSString *selectorName = [NSString stringWithUTF8String:name];
    SEL selector = NSSelectorFromString([selectorName hasSuffix:@":"] ? selectorName : [selectorName stringByAppendingString:@":"]);
    if ([NSString instancesRespondToSelector:selector]) {
        sqlite3_create_collation(handle, name, SQLITE_UTF8, selector, DBCollationCompare);
    }
}

@implementation NSNull (DBValue)

- (void)setResultOfFunctionWithContext:(sqlite3_context *)context {
    sqlite3_result_null(context);
}

- (BOOL)bindToStatement:(DBStatement *)statement atParameterIndex:(NSUInteger)parameterIndex {
    return (SQLITE_OK == sqlite3_bind_null(statement.handle, parameterIndex));
}

- (NSNumber *)numberValue {
    return nil;
}

- (NSString *)stringValue {
    return nil;
}

- (NSData *)dataValue {
    return nil;
}

@end

@implementation NSNumber (DBValue)

- (void)setResultOfFunctionWithContext:(sqlite3_context *)context {
    if (CFNumberIsFloatType((CFNumberRef)self)) {
        sqlite3_result_double(context, [self doubleValue]);
    } else {
        sqlite3_result_int64(context, [self longLongValue]);
    }
}

- (BOOL)bindToStatement:(DBStatement *)statement atParameterIndex:(NSUInteger)parameterIndex {
    if (CFNumberIsFloatType((CFNumberRef)self)) {
        return (SQLITE_OK == sqlite3_bind_double(statement.handle, parameterIndex, [self doubleValue]));
    } else {
        return (SQLITE_OK == sqlite3_bind_int64(statement.handle, parameterIndex, [self longLongValue]));
    }
}

- (NSNumber *)numberValue {
    return [[self retain] autorelease];
}

- (NSData *)dataValue {
    return [[self stringValue] dataValue];
}

@end

@implementation NSString (DBValue)

- (void)setResultOfFunctionWithContext:(sqlite3_context *)context {
    sqlite3_result_text(context, [self UTF8String], -1, SQLITE_TRANSIENT);
}

- (BOOL)bindToStatement:(DBStatement *)statement atParameterIndex:(NSUInteger)parameterIndex {
    return (SQLITE_OK == sqlite3_bind_text(statement.handle, parameterIndex, [self UTF8String], -1, SQLITE_TRANSIENT));
}

- (NSNumber *)numberValue {
    long long longLongValue = 0;
    NSScanner *scanner = [NSScanner scannerWithString:self];
    NSNumber *numberWithLongLong = ([scanner scanLongLong:&longLongValue] ? [NSNumber numberWithLongLong:longLongValue] : nil);
    
    double doubleValue = 0.0;
    [scanner setScanLocation:0];
    NSNumber *numberWithDouble = ([scanner scanDouble:&doubleValue] ? [NSNumber numberWithDouble:doubleValue] : nil);
    
    if (numberWithLongLong && numberWithDouble) {
        return ([numberWithLongLong isEqualToNumber:numberWithDouble] ? numberWithLongLong : numberWithDouble);
    } else {
        return (numberWithLongLong ? numberWithLongLong : numberWithDouble);
    }
}

- (NSString *)stringValue {
    return [[self retain] autorelease];
}

- (NSData *)dataValue {
    return [self dataUsingEncoding:NSUTF8StringEncoding];
}

@end

@implementation NSData (DBValue)

- (void)setResultOfFunctionWithContext:(sqlite3_context *)context {
    sqlite3_result_blob(context, [self bytes], [self length], SQLITE_TRANSIENT);
}

- (BOOL)bindToStatement:(DBStatement *)statement atParameterIndex:(NSUInteger)parameterIndex {
    return (SQLITE_OK == sqlite3_bind_blob(statement.handle, parameterIndex, [self bytes], [self length], SQLITE_TRANSIENT));
}

- (NSNumber *)numberValue {
    return [[self stringValue] numberValue];
}

- (NSString *)stringValue {
    return [[[NSString alloc] initWithData:self encoding:NSUTF8StringEncoding] autorelease];
}

- (NSData *)dataValue {
    return [[self retain] autorelease];
}

@end

@implementation DBFunction

@synthesize connection = db_connection;
@synthesize selector   = db_selector;

+ (NSArray *)functionsWithConnection:(DBConnection *)connection {
    unsigned methodCount = 0;
    Method *methodList = class_copyMethodList([DBFunction class], &methodCount);
    NSMutableArray *result = [NSMutableArray arrayWithCapacity:methodCount];
    @try {
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
        for (unsigned methodIndex = 0; methodIndex < methodCount; methodIndex++) {
            SEL selector = method_getName(methodList[methodIndex]);
            if (sel_isEqual(selector, @selector(connection)) ||
                sel_isEqual(selector, @selector(setConnection:)) ||
                sel_isEqual(selector, @selector(selector)) ||
                sel_isEqual(selector, @selector(setSelector:)) ||
                sel_isEqual(selector, @selector(description))) {
                continue;
            }
            
            DBFunction *function = [[[DBFunction alloc] init] autorelease];
            function.connection = connection;
            function.selector = selector;
            [result addObject:function];
            
            NSString *selectorName = NSStringFromSelector(selector);
            NSString *functionName = [selectorName substringToIndex:[selectorName rangeOfString:@":"].location];
            NSMethodSignature *signature = [DBFunction instanceMethodSignatureForSelector:selector];
            NSInteger parameterCount = ([selectorName hasSuffix:@"___:"] ? -1 : ([signature numberOfArguments] - 3));
            BOOL isScalar = DBTypeIsObject([signature methodReturnType]);
            BOOL isAggregate = DBTypeIsVoid([signature methodReturnType]);
            
            NSAssert1(isScalar || isAggregate, @"Invalid return type: %@", function);
            NSAssert1([signature numberOfArguments] >= 3, @"Invalid number of arguments: %@", function);
            for (NSUInteger argumentIndex = 2; argumentIndex < [signature numberOfArguments]; argumentIndex++) {
                NSAssert1(DBTypeIsObject([signature getArgumentTypeAtIndex:argumentIndex]), @"Invalid argument type: %@", function);
            }
            
            if (SQLITE_OK != sqlite3_create_function(connection.handle, [functionName UTF8String], parameterCount, SQLITE_UTF8, function,
                                                     (isScalar ? DBFunctionInvokeScalar : NULL),
                                                     (isAggregate ? DBFunctionStepAggregate : NULL),
                                                     (isAggregate ? DBFunctionFinalizeAggregate : NULL))) {
                @throw [connection exceptionWithFormat:@"Failed to register the %@ function.", function];
            }
        }
        [pool drain];
    } @finally {
        if (methodList) {
            free(methodList);
        }
    }
    return result;
}

+ (id)invokeFunction:(DBFunction *)function userInfo:(NSMutableDictionary *)userInfo valueList:(sqlite3_value **)valueList valueCount:(NSUInteger)valueCount {
    NSMethodSignature *signature = [DBFunction instanceMethodSignatureForSelector:function.selector];
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    [invocation setTarget:function];
    [invocation setSelector:function.selector];
    [invocation setArgument:&userInfo atIndex:2];
    for (NSUInteger valueIndex = 0, argumentIndex = 3; argumentIndex < [signature numberOfArguments]; valueIndex++, argumentIndex++) {
        id argument = nil;
        if (valueIndex < valueCount) {
            if (((argumentIndex + 1) == [signature numberOfArguments]) && [NSStringFromSelector(function.selector) hasSuffix:@"___:"]) {
                for (argument = [NSMutableArray array]; valueIndex < valueCount; valueIndex++) {
                    [argument addObject:[DBFunction valueFromSQLite:valueList[valueIndex]]];
                }
            } else {
                argument = [DBFunction valueFromSQLite:valueList[valueIndex]];
            }
        }
        [invocation setArgument:&argument atIndex:argumentIndex];
    }
    [invocation invoke];
    
    id result = nil;
    if (DBTypeIsObject([signature methodReturnType])) {
        [invocation getReturnValue:&result];
    } else {
        result = [userInfo valueForKey:SQLiteResultKey];
    }
    return (result ? result : [NSNull null]);
}

+ (void)setUserInfo:(NSMutableDictionary *)userInfo forScalarWithContext:(sqlite3_context *)context valueCount:(NSUInteger)valueCount {
    for (NSUInteger valueIndex = 0; valueIndex < valueCount; valueIndex++) {
        NSString *key = [[NSNumber numberWithUnsignedInteger:valueIndex] stringValue];
        id metadata = [userInfo valueForKey:key];
        sqlite3_set_auxdata(context, valueIndex,
                            (metadata ? (void *)CFRetain(metadata) : NULL),
                            (metadata ? (sqlite3_destructor_type)CFRelease : NULL));
    }
}

+ (NSMutableDictionary *)userInfoForScalarWithContext:(sqlite3_context *)context valueCount:(NSUInteger)valueCount {
    NSMutableDictionary *result = [NSMutableDictionary dictionaryWithCapacity:valueCount];
    for (NSUInteger valueIndex = 0; valueIndex < valueCount; valueIndex++) {
        NSString *key = [[NSNumber numberWithUnsignedInteger:valueIndex] stringValue];
        id metadata = sqlite3_get_auxdata(context, valueIndex);
        [result setValue:metadata forKey:key];
    }
    return result;
}

+ (NSMutableDictionary *)userInfoForAggregateWithContext:(sqlite3_context *)context {
    NSMutableDictionary **result = sqlite3_aggregate_context(context, sizeof(NSMutableDictionary *));
    if (!(*result)) {
        *result = [NSMutableDictionary dictionary];
        CFRetain(*result);
    }
    return *result;
}

+ (id)valueFromSQLite:(sqlite3_value *)value {
    NSInteger valueType = sqlite3_value_type(value);
    switch (valueType) {
        case SQLITE_NULL:
            return [NSNull null];
        case SQLITE_INTEGER:
            return [NSNumber numberWithLongLong:sqlite3_value_int64(value)];
        case SQLITE_FLOAT:
            return [NSNumber numberWithDouble:sqlite3_value_double(value)];
        case SQLITE_TEXT: {
            const char *text = (const char *)sqlite3_value_text(value);
            id result = (text ? [NSString stringWithUTF8String:text] : nil);
            return (result ? result : [NSNull null]);
        }
        case SQLITE_BLOB: {
            const void *bytes = sqlite3_value_blob(value);
            NSUInteger length = sqlite3_value_bytes(value);
            id result = ((bytes && length) ? [NSData dataWithBytes:bytes length:length] : nil);
            return (result ? result : [NSNull null]);
        }
        default:
            NSAssert1(NO, @"Unsupported value type: %i", (int)valueType);
            return [NSNull null];
    }
}

- (NSString *)description {
    return [NSString stringWithFormat:@"-[%@ %@]", NSStringFromClass([self class]), NSStringFromSelector(self.selector)];
}

- (id)lower:(NSMutableDictionary *)userInfo :(id)value {
    return [[value stringValue] lowercaseString];
}

- (id)upper:(NSMutableDictionary *)userInfo :(id)value {
    return [[value stringValue] uppercaseString];
}

@end

@implementation DBConnection

@synthesize path      = db_path;
@synthesize handle    = db_handle;
@synthesize delegate  = db_delegate;
@synthesize functions = db_functions;

- (id)initWithPath:(NSString *)path {
    if (self = [super init]) {
        @try {
            NSParameterAssert(path);
            sqlite3 *handle = NULL;
            if (SQLITE_OK != sqlite3_open([path UTF8String], &handle)) {
                self.handle = handle;
                @throw [self exceptionWithFormat:@"Failed to connect to the \"%@\" database.", path];
            }
            self.path = path;
            self.handle = handle;
            self.functions = [DBFunction functionsWithConnection:self];
            
            sqlite3_extended_result_codes(self.handle, YES);
            sqlite3_collation_needed(self.handle, NULL, DBCollationNeeded);
            sqlite3_create_collation(self.handle, "BINARY", SQLITE_UTF8, @selector(localizedCompare:), DBCollationCompare);
            sqlite3_progress_handler(self.handle, 10, DBConnectionShouldInterrupt, self);
            sqlite3_busy_handler(self.handle, DBConnectionShouldRetryLock, self);
        } @catch (NSException *exception) {
            [[self close] release];
            @throw;
        }
    }
    return self;
}

- (id)close {
    if (self.handle) {
        if (SQLITE_OK != sqlite3_close(self.handle)) {
            @throw [self exceptionWithFormat:@"Failed to disconnect from the \"%@\" database.", self.path];
        }
        self.handle = NULL;
    }
    return self;
}

- (void)dealloc {
    self.path = nil;
    self.functions = nil;
    [super dealloc];
}

- (NSString *)description {
    return [NSString stringWithFormat:@"%@ \"%@\"", NSStringFromClass([self class]), self.path];
}

- (id)executeSQL:(NSString *)sql {
    id result = nil;
    DBStatement *statement = nil;
    @try {
        statement = [[DBStatement alloc] initWithConnection:self SQL:sql];
        result = [statement execute];
    } @finally {
        [[statement close] release];
    }
    return result;
}

- (BOOL)withinTransaction {
    NSParameterAssert(self.handle);
    return (sqlite3_get_autocommit(self.handle) ? NO : YES);
}

- (NSNumber *)lastInsertRowid {
    NSParameterAssert(self.handle);
    return [NSNumber numberWithLongLong:sqlite3_last_insert_rowid(self.handle)];
}

- (NSUInteger)recentChanges {
    NSParameterAssert(self.handle);
    return sqlite3_changes(self.handle);
}

- (NSUInteger)totalChanges {
    NSParameterAssert(self.handle);
    return sqlite3_total_changes(self.handle);
}

- (NSException *)exceptionWithFormat:(NSString *)format, ... {
    va_list argList;
    va_start(argList, format);
    NSString *reason = [[[NSString alloc] initWithFormat:format arguments:argList] autorelease];
    va_end(argList);
    
    NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithCapacity:3];
    [userInfo setValue:[NSNumber numberWithInteger:sqlite3_errcode(self.handle)] forKey:SQLiteErrorCodeKey];
    [userInfo setValue:(sqlite3_errmsg(self.handle) ? [NSString stringWithUTF8String:sqlite3_errmsg(self.handle)] : nil) forKey:SQLiteErrorMessageKey];
    [userInfo setValue:[NSString stringWithUTF8String:sqlite3_libversion()] forKey:SQLiteVersionKey];
    return [NSException exceptionWithName:DBException reason:reason userInfo:userInfo];
}

@end

@implementation DBStatement

@synthesize connection    = db_connection;
@synthesize sql           = db_sql;
@synthesize handle        = db_handle;
@synthesize row           = db_row;
@synthesize mutationCount = db_mutationCount;

- (id)initWithConnection:(DBConnection *)connection SQL:(NSString *)sql {
    if (self = [super init]) {
        @try {
            NSParameterAssert(connection.handle);
            NSParameterAssert(sql);
            sqlite3_stmt *handle = NULL;
            if (SQLITE_OK != sqlite3_prepare_v2(connection.handle, [sql UTF8String], -1, &handle, NULL)) {
                @throw [connection exceptionWithFormat:@"Failed to compile the \"%@\" statement.", sql];
            } else if (!handle) {
                [self release];
                return nil;
            }
            self.connection = connection;
            self.sql = sql;
            self.handle = handle;
        } @catch (NSException *exception) {
            [self release];
            @throw;
        }
    }
    return self;
}

- (id)close {
    if (self.handle) {
        sqlite3_finalize(self.handle);
        self.handle = NULL;
    }
    return self;
}

- (void)dealloc {
    self.sql = nil;
    self.row = nil;
    [super dealloc];
}

- (NSString *)description {
    return [NSString stringWithFormat:@"%@ \"%@\"", NSStringFromClass([self class]), self.sql];
}

- (void)reset {
    NSParameterAssert(self.handle);
    sqlite3_reset(self.handle);
    self.row = nil;
    self.mutationCount++;
}

- (id)execute {
    id result = nil;
    for (id row in self) {
        result = [[[row valueForKey:@"0"] retain] autorelease];
        [self reset];
        break;
    }
    return result;
}

- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)result count:(NSUInteger)unused {
    NSParameterAssert(self.handle);
    if (0 == state->state) {
        [self reset];
    } else if (DBConnectionShouldInterrupt(self.connection)) {
        sqlite3_interrupt(self.connection.handle);
    }
    switch (0xff & sqlite3_step(self.handle)) {
        case SQLITE_ROW: {
            NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; {
                NSUInteger columnCount = sqlite3_column_count(self.handle);
                self.row = [NSMutableDictionary dictionaryWithCapacity:((2 * columnCount) + 1)];
                NSMutableArray *array = [NSMutableArray arrayWithCapacity:columnCount];
                for (NSUInteger columnIndex = 0; columnIndex < columnCount; columnIndex++) {
                    id value = [self valueAtColumnIndex:columnIndex];
                    const char *columnName = sqlite3_column_name(self.handle, columnIndex);
                    if (columnName) {
                        [self.row setObject:value forKey:[NSString stringWithUTF8String:columnName]];
                    }
                    [self.row setValue:value forKey:[[NSNumber numberWithUnsignedInteger:columnIndex] stringValue]];
                    [array addObject:value];
                }
                [self.row setValue:array forKey:@"*"];
            } [pool drain];
            
            state->state++;
            state->itemsPtr = result;
            state->mutationsPtr = state->extra;
            state->extra[0] = self.mutationCount;
            result[0] = self.row;
            return 1;
        }
        case SQLITE_OK:
        case SQLITE_DONE:
            [self reset];
            return 0;
        default:
            [self reset];
			NSString* errorMessage = [[NSString alloc] initWithCString: sqlite3_errmsg(self.handle)];
			
            @throw [self.connection exceptionWithFormat:errorMessage];//@"Failed to execute the \"%@\" statement.", self.sql];
    }
}

- (id)valueAtColumnIndex:(NSUInteger)columnIndex {
    NSInteger columnType = sqlite3_column_type(self.handle, columnIndex);
    switch (columnType) {
        case SQLITE_NULL:
            return [NSNull null];
        case SQLITE_INTEGER:
            return [NSNumber numberWithLongLong:sqlite3_column_int64(self.handle, columnIndex)];
        case SQLITE_FLOAT:
            return [NSNumber numberWithDouble:sqlite3_column_double(self.handle, columnIndex)];
        case SQLITE_TEXT: {
            const char *text = (const char *)sqlite3_column_text(self.handle, columnIndex);
            id result = (text ? [NSString stringWithUTF8String:text] : nil);
            return (result ? result : [NSNull null]);
        }
        case SQLITE_BLOB: {
            const void *bytes = sqlite3_column_blob(self.handle, columnIndex);
            NSUInteger length = sqlite3_column_bytes(self.handle, columnIndex);
            id result = ((bytes && length) ? [NSData dataWithBytes:bytes length:length] : nil);
            return (result ? result : [NSNull null]);
        }
        default:
            NSAssert1(NO, @"Unsupported column type: %i", (int)columnType);
            return [NSNull null];
    }
}

- (void)setValue:(id)value forKey:(NSString *)key {
	
    NSParameterAssert(self.handle);
    NSParameterAssert(key);
    NSUInteger parameterIndex = sqlite3_bind_parameter_index(self.handle, [key UTF8String]);
    if (0 == parameterIndex) {
        parameterIndex = [[key numberValue] unsignedIntegerValue];
    }
    if (!value) {
        value = [NSNull null];
    }
    if (![value bindToStatement:self atParameterIndex:parameterIndex]) {
        @throw [self.connection exceptionWithFormat:@"Failed to set parameter \"%@\" of the \"%@\" statement.", key, self.sql];
    }
    self.mutationCount++;
}

@end
